#ifndef __M_SESSION_H__
#define __M_SESSION_H__

#include "log.hpp"
#include <unordered_map>
#include <memory>
#include <websocketpp/server.hpp>
#include <websocketpp/config/asio_no_tls.hpp>

/* session状态 */
typedef enum
{
    UNLOGIN,
    LOGIN
} ss_status;

typedef websocketpp::server<websocketpp::config::asio> wsserver_t;

class session
{
public:
    session(const uint64_t ssid) : _ssid(ssid)
    {
        DBG_LOG("session %p 创建成功", this);
    }
    ~session()
    {
        DBG_LOG("session %p 销毁成功", this);
    }

    /* 设置用户状态——登录or未登录 */
    void set_state(const ss_status state)
    {
        _state = state;
    }
    /* 设置session所属用户 */
    void set_user(const uint64_t uid)
    {
        _uid = uid;
    }
    /* 设置定时器 */
    void set_timer(const wsserver_t::timer_ptr &tp)
    {
        _tp = tp;
    }

    /* 获取session id */
    uint64_t ssid() const
    {
        return _ssid;
    }
    /* 获取session所绑定的用户id */
    uint64_t get_uid() const
    {
        return _uid;
    }
    /* 获取当前用户是否登录 */
    bool is_login() const
    {
        return _state == LOGIN;
    }
    /* 获取定时器 */
    wsserver_t::timer_ptr get_timer() const
    {
        return _tp;
    }

private:
    uint64_t _ssid;            // session id，session管理类对象分配
    uint64_t _uid;             // session所属用户的id
    ss_status _state;          // session状态——未登录or已登录
    wsserver_t::timer_ptr _tp; // session关联的定时器
};

#define SESSION_TIMEOUT 30000 // 定时器到期时间 30s
#define SESSION_FOREVER -1    // 永久存在

using session_ptr = std::shared_ptr<session>; // session的智能指针对象
class session_manage
{
public:
    session_manage(wsserver_t *wsvr) : _server(wsvr), _next_ssid(1)
    {
        DBG_LOG("session 管理器初始化完成!");
    }
    ~session_manage()
    {
        DBG_LOG("session 管理器销毁！");
    }
    /* 为指定用户创建session——登陆的时候创建 */
    session_ptr create_session(const uint64_t uid, const ss_status state)
    {
        std::unique_lock<std::mutex> lock(_mutex);
        // 1. 创建session，设置session状态和所属用户
        session_ptr ssp(new session(_next_ssid));
        ssp->set_state(state);
        ssp->set_user(uid);
        // 2. 建立ssid和session的关系
        _sessions.insert(std::make_pair(_next_ssid, ssp));
        // 3. 创建完session，session计数器自增
        ++_next_ssid;
        return ssp;// 返回创建的session的智能指针对象
    }
    /* 添加已有的session */
    void append_session(const session_ptr& ssp)
    {
        std::unique_lock<std::mutex> lock(_mutex);
        _sessions.insert(std::make_pair(ssp->ssid(), ssp));
    }
    /* 通过ssid查找session——一般是用户登陆时携带ssid，服务器通过ssid查找session */
    session_ptr get_session_by_ssid(const uint64_t ssid)
    {
        std::unique_lock<std::mutex> lock(_mutex);
        auto it = _sessions.find(ssid);
        if (it == _sessions.end())
            return session_ptr();

        return it->second;
    }
    /* 设置session过期时间 */
    void set_session_expired_time(const uint64_t ssid, const int msec = SESSION_TIMEOUT/* 毫秒 */)
    {
        /* 此方法依赖于websocketpp的定时器来管理session的生命周期 */
        /*
            http协议通信中，用户注册和登陆时，建立的都是短链接，session都是被设置了定时删除任务的，具备有限的生命周期，指定时间无通信后删除。
            如果使用了websocketpp进行通信，就会建立长链接，此时就需要设置session为永久存在。

            也即是说，在用户登陆成功后，server会为其建立一个session，这个session是有有限的生命周期的。而一旦用户进入大厅后，就会使用websocket协议
        进行长连接通信，session应该永久存在，session的生命周期也会转变为永久性的。
            而当用户对战结束退出时，就会把session的生命周期重新设置为有限的时间，一定时间无通信后删除。
        */

        // 获取session
        session_ptr ssp = get_session_by_ssid(ssid);
        if (ssp.get() == nullptr)
        {
            DBG_LOG("set_session_expired_time 获取session失败！");
            return;
        }
        // 获取定时器
        wsserver_t::timer_ptr tp = ssp->get_timer();
        // 定时器对象为空即为session永久存在

        // 1. session永久存在时，设置永久存在——即不设置
        if (tp.get() == nullptr && msec == SESSION_FOREVER)
            ;
        // 2. session永久存在时，设置指定事件之后被删除的定时任务
        else if (tp.get() == nullptr && msec != SESSION_FOREVER)
        {
            // 创建定时任务
            wsserver_t::timer_ptr tmp_tp = _server->set_timer(msec, std::bind(&session_manage::remove_session, this, ssid));
            // 设置定时任务
            ssp->set_timer(tmp_tp);
        }
        // 3. session被设置了定时删除任务，设置session为永久存在
        else if (tp.get() != nullptr && msec == SESSION_FOREVER)
        {
            // 删除定时任务——tp->cancle()会先在内部进行一些状态切换，然后立即执行设置的定时任务——即将session从session管理器中移除
            tp->cancel();
            // 重新添加session信息到session管理器
            ssp->set_timer(wsserver_t::timer_ptr()); // 添加空的定时任务
            /* _sessions.insert(std::make_pair(ssp->ssid(), ssp));
            因为tp->cancel()取消定时任务不是立即取消，这就可能导致_sessions.insert()之后，
            取消了新insert的定时任务，所以这里不能立即执行insert() */

            _server->set_timer(0, std::bind(&session_manage::append_session, this, ssp)); // 设置一个定时器来执行添加任务
        }
        // 4. session设置了定时删除任务，重置session定时删除时间
        else if(tp.get() != nullptr && msec != SESSION_FOREVER)
        {
            // 取消session关联的定时器的定时任务
            tp->cancel();
            ssp->set_timer(wsserver_t::timer_ptr());
            // 设置一个定时器来执行添加session的任务
            _server->set_timer(0, std::bind(&session_manage::append_session, this, ssp));

            // 添加新的定时任务
            wsserver_t::timer_ptr tmp_tp = _server->set_timer(msec, std::bind(&session_manage::remove_session, this, ssid));
            // 重新设置session关联的定时器
            ssp->set_timer(tmp_tp);
        }
    }
    /* 销毁sesion——过期自动销毁 */
    void remove_session(uint64_t ssid)
    {
        std::unique_lock<std::mutex> lock(_mutex);
        _sessions.erase(ssid);
    }

private:
    uint64_t _next_ssid;                                 // 分配给session的session id
    std::mutex _mutex;                                   // 互斥锁
    std::unordered_map<uint64_t, session_ptr> _sessions; // session管理器——ssid和session的映射
    wsserver_t *_server;                                 // websocket服务端
};

#endif